/*	--- 模板方法 --- 


#	定义【一个操作中】的【算法的骨架】，而将一些【步骤】延迟到【子类中】，使得【子类】可以【不改变】一个【算法的结构】即可【重定义】该算法的【某些特定步骤】。

说白了,就是建立一棵【算法树】

=====================================================================

【模板方法】（Template Method）是一个比较简单的模式。

它的主要思想是，定义【一个操作】的【一系列步骤】，对于某些【暂时确定不下来的步骤】，就留给【子类】去实现好了，这样【不同的子类】就可以【定义】出【不同的步骤】。

因此，【模板方法的核心】在于【定义一个“骨架”】。

-----------------

我们还是举例说明。

假设我们【开发】了一个【从数据库读取设置】的【类】： */
public class Setting {
	public final String getSetting(String key) {
		String value = readFromDatabase(key);
		return value;
	}

	private String readFromDatabase(String key) {
		// TODO:

	}
}

/*由于从【数据库】读取数据【较慢】，我们可以考虑【把读取的设置】缓存起来，这样【下一次读取】同样的key就【不必再访问数据库】了。

但是【怎么实现缓存】，暂时没想好，但不妨碍我们先写出【使用缓存的代码】： */
public class Setting {
	public final String getSetting(String key) {
		// 先从Cache中读取
		String value = lookupCache(key);

		if (value == null) {
			// 从Cache中未找到,则重新去数据库读取
			value = readFromDatabase(key);
			System.out.println("[DEBUG] load from db: " + key + " = " + value);

			// 放入缓存
			putIntoCache(key, value);
		} else {
			System.out.println("[DEBUG] load from cache: " + key + " = " + value);
		}

		return value;
	}
}


/*
整个流程没有问题，

但是，
	lookupCache(key) 
	putIntoCache(key, value) 

这两个方法还根本没实现，怎么编译通过？

这个不要紧，我们【声明抽象方法】就可以： */
public abstract class AbstractSetting {
	public final String getSetting(String key) {
		String value = lookupCache(key);

		if (value == null) {
			value = readFromDatabase(key);
			putIntoCache(key, value);
		}

		return value;
	}

	protected abstract String lookupCache(String key);

	protected abstract void putIntoCache(String key, String value);
}


/*
因为声明了【抽象方法】，自然【整个类】也必须是【抽象类】。

如何实现
	lookupCache(key)
	putIntoCache(key, value)

这【两个方法】就交给【子类】了。

【子类】其实并不关心【核心代码】 getSetting(key)的逻辑，

它只需要关心【如何完成】两个【小小的子任务(子类)】就可以了。

----------------

假设我们希望【用一个Map】做【缓存】，那么可以【写一个LocalSetting】： */
public class LocalSetting extends AbstractSetting {
	private Map<String, String> cache = new HashMap<>();

	protected String lookupCache(String key) {
		return cache.get(key);
	}

	protected void putIntoCache(String key, String value) {
		cache.put(key, value);
	}
}


/*
如果我们要【使用Redis做缓存】，那么可以【再写一个RedisSetting】： */
public class RedisSetting extends AbstractSetting {
	private RedisClient client = RedisClient.create("redis://localhost:6379");

	protected String lookupCache(String key) {
		try (StatefulRedisConnection<String, String> connection = client.connect()) {
			RedisCommands<String, String> commands = connection.sync();
			return commands.get(key);
		}
	}

	protected void putIntoCache(String key, String value) {
		try (StatefulRedisConnection<String, String> connection = client.connect()) {
			RedisCommnds<String, String> commands = connection.sync();
			commands.set(key, value);
		}
	}
}

/*
【客户端代码】使用【本地缓存】的代码这么写： */
AbstractSetting setting1 = new LocalSetting();
System.out.println("test = " + setting1.getSetting("test"));
System.out.println("test = " + setting1.getSetting("test"));

/*
要改成Redis缓存，只需要把LocalSetting替换为RedisSetting： */
AbstractSetting setting2 = new RedisSetting();
System.out.println("autosave = " + setting2.getSetting("autosave"));
System.out.println("autosave = " + setting2.getSetting("autosave"));


/*
可见，【模板方法】的核心思想是：【父类】定义【骨架】，【子类】实现【某些细节】。

为了防止子类重写父类的骨架方法，可以在父类中对骨架方法使用final。对于需要子类实现的抽象方法，一般声明为protected，使得这些方法对外部客户端不可见。

Java标准库也有很多模板方法的应用。在集合类中，AbstractList和AbstractQueuedSynchronizer都定义了很多通用操作，子类只需要实现某些必要方法。


=======================================================


#	练习

使用模板方法增加一个使用Guava Cache的子类。 */




/*=================================================


#	思考

能否将readFromDatabase()作为模板方法，使得子类可以选择从数据库读取还是从文件读取。

再思考如果既可以扩展缓存，又可以扩展底层存储，会不会出现子类数量爆炸的情况？如何解决？ */




/*==============================================================

#	----- 模版方法 の 小结 ----- 

1. 【模板方法】是一种【高层定义骨架】，【底层实现细节】的【设计模式】，适用于【流程固定】，但【某些步骤不确定】或【可替换】的情况。

----------

模版方法 = 一棵【算法树】

【模板方法的核心】在于【定义一个“骨架”】
	-- 算法树の核心 在于 定义一个Root

---------

算法树の模板: */
// Abstract
public abstract class AbstractSetting {
	public final String getSetting(String key) {
		String value = lookupCache(key);

		if (value == null) {
			value = readFromDatabase(key);
			putIntoCache(key, value);
		}

		return value;
	}

	protected abstract String lookupCache(String key);

	protected abstract void putIntoCache(String key, String value);
}

// Local extendsAbstract Abstract
public class LocalSetting extends AbstractSetting {
	private Map<String, String> cache = new HashMap<>();

	protected String lookupCache(String key) {
		return cache.get(key);
	}

	protected void putIntoCache(String key, String value) {
		cache.put(key, value);
	}
}

// Redis extends Abstract
public class RedisSetting extends AbstractSetting {
	private RedisClient client = RedisClient.create("redis://localhost:6379");

	protected String lookupCache(String key) {
		try (StatefulRedisConnection<String, String> connection = client.connect()) {
			RedisCommands<String, String> commands = connection.sync();
			return commands.get(key);
		}
	}

	protected void putIntoCache(String key, String value) {
		try (StatefulRedisConnection<String, String> connection = client.connect()) {
			RedisCommnds<String, String> commands = connection.sync();
			commands.set(key, value);
		}
	}
}

AbstractSetting setting1 = new LocalSetting();
System.out.println("test = " + setting1.getSetting("test"));
System.out.println("test = " + setting1.getSetting("test"));

AbstractSetting setting2 = new RedisSetting();
System.out.println("autosave = " + setting2.getSetting("autosave"));
System.out.println("autosave = " + setting2.getSetting("autosave"));



























